package qc.module.qms.service.pointdata;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.constants.QmsConstant;
import qc.common.core.enums.qms.*;
import qc.common.core.exception.QCPromptException;
import qc.common.core.unify.QCUnifyReturnValue;
import qc.common.core.utils.IdentifierUtil;
import qc.common.core.utils.LocalDateTimeUtil;
import qc.common.core.utils.QmsNumericUtil;
import qc.common.core.utils.QmsTimeUtil;
import qc.module.qms.dto.alarm.QmsPointDataValueAlarmJudgeResultDto;
import qc.module.qms.dto.alarm.QmsPointDataValueAlarmRuleJudgeResultDto;
import qc.module.qms.dto.mq.QmsMqPointDataMessageDto;
import qc.module.qms.dto.pointdata.QmsPointDataManageResultDto;
import qc.module.qms.dto.pointdata.QmsPointDataSaveAffectDto;
import qc.module.qms.dto.pointdata.QmsPointNumericDataDto;
import qc.module.qms.dto.pointdata.QmsPointNumericSaveDataDto;
import qc.module.qms.entity.*;
import qc.module.qms.mapper.QmsPointNumericDataMapper;
import qc.module.qms.service.QmsBasicConfigCacheGetService;
import qc.module.qms.service.alarm.QmsPointDataValueAlarmJudgeService;
import qc.module.qms.service.producer.QmsDataFlowLogProducer;
import qc.module.qms.service.producer.QmsPointDataProducer;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * QMS点号数据管理Service，提供点号数据的保存、删除、修改（数值、数据标记）
 * 对点号数据最终进行实质性操作的均记录到点号数据日志表中(点号数据日志使用消息队列)
 *
 * @author QuCheng Tech
 * @since 2025/8/20
 */
@Service
public class QmsPointDataManageService {
    private static final Logger logger = LoggerFactory.getLogger(QmsPointDataManageService.class);

    @Autowired
    private QmsPointDataProducer pointDataProducer;

    @Autowired
    private QmsDataFlowLogProducer dataFlowLogProducer;

    @Autowired
    private QmsPointDataQueryService pointDataQueryService;

    @Autowired
    private QmsBasicConfigCacheGetService basicConfigCacheGetService;

    @Autowired
    private QmsPointDataLatestService pointDataLatestService;

    @Autowired
    private QmsPointRealNumericDataService pointRealNumericDataService;

    @Autowired
    private QmsPointHourNumericDataService pointHourNumericDataService;

    @Autowired
    private QmsPointDayNumericDataService pointDayNumericDataService;

    @Autowired
    private QmsPointWeekNumericDataService pointWeekNumericDataService;

    @Autowired
    private QmsPointTendaysNumericDataService pointTendaysNumericDataService;

    @Autowired
    private QmsPointMonthNumericDataService pointMonthNumericDataService;

    @Autowired
    private QmsPointQuarterNumericDataService pointQuarterNumericDataService;

    @Autowired
    private QmsPointYearNumericDataService pointYearNumericDataService;

    @Autowired
    private QmsPointDataValueAlarmJudgeService pointDataValueAlarmJudgeService;

    /**
     * 保存系统自动进行数值计算（原始数据触发、定时点号计算）的点号数据，一次保存一个点号的数据集合，支持单条或多条操作
     * 计算点号数据的数据质量均为有效数据，再根据点号数据值的上下限值判断是否为可疑数据
     *
     * @param pointid    点号编码
     * @param tmseg      点号时段类型
     * @param datas      点号数据集合
     * @param optype     操作类型；系统自动操作：原始数据触发计算、定时点号数据计算
     * @param flowid     流程全局ID
     * @param fromNodeId 流程上一步节点ID
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto 返回结果中包含是否成功，实际结果受影响的数据条数
     * @author QuCheng Tech
     * @since 2025/10/1
     */
    public QmsPointDataManageResultDto saveSystemCalcNumericDatas(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericSaveDataDto> datas,
                                                                  QmsDataFlowOperateTypeEnum optype, String flowid, String fromNodeId) {
        return saveSystemCalcNumericDatas(pointid, tmseg, datas, optype, flowid, fromNodeId, false, null);
    }

    /**
     * 保存人工操作/原始数据处理/定时点号数据计算触发的系统自动数值计算（指定时段原始数据处理、指定时段点号数据计算）的点号数据，一次保存一个点号的数据集合，支持单条或多条操作
     * 计算点号数据的数据质量均为有效数据，再根据点号数据值的上下限值判断是否为可疑数据
     *
     * @param pointid    点号编码
     * @param tmseg      点号时段类型
     * @param datas      点号数据集合
     * @param optype     操作类型；人工操作：重新处理原始数据、重新点号数据计算、人工录入/修改点号数据
     * @param flowid     流程全局ID
     * @param fromNodeId 流程上一步节点ID
     * @param userName   操作用户名称
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto
     * @author QuCheng Tech
     * @since 2025/10/14
     */
    public QmsPointDataManageResultDto saveSystemCalcNumericDatas(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericSaveDataDto> datas,
                                                                  QmsDataFlowOperateTypeEnum optype, String flowid, String fromNodeId, boolean isManual, String userName) {
        return savePointDatas(pointid, tmseg, convertNumericDatasToPointDatas(datas), optype, flowid, fromNodeId, true, userName);
    }

    /**
     * 保存人工录入或修改的点号数据，一次保存一个点号的数据集合，支持单条或多条操作
     *
     * @param pointid  点号编码
     * @param tmseg    点号时段类型
     * @param datas    点号数据集合
     * @param userName 操作用户名称
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto 返回结果中包含是否成功，实际结果受影响的数据条数
     * @author QuCheng Tech
     * @since 2025/10/1
     */
    public QmsPointDataManageResultDto saveManualEntryNumericDatas(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericSaveDataDto> datas, String userName) {
        return savePointDatas(pointid, tmseg, convertNumericDatasToPointDatas(datas), QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY, null, null, true, userName);
    }

    /**
     * 转换计算出的数值数据为点号数据集合，保持特征值字段为null，uptm为当前时间
     *
     * @param numericDatas 数值数据集合
     * @return java.util.List<qc.module.qms.dto.pointdata.QmsPointNumericDataDto>
     * @author QuCheng Tech
     * @since 2025/10/17
     */
    List<QmsPointNumericDataDto> convertNumericDatasToPointDatas(List<QmsPointNumericSaveDataDto> numericDatas) {
        List<QmsPointNumericDataDto> pointDatas = null;
        if (numericDatas != null && numericDatas.size() > 0x0) {
            LocalDateTime now = LocalDateTimeUtil.getNow();

            pointDatas = new LinkedList<>();
            for (QmsPointNumericSaveDataDto numericData : numericDatas) {
                if (numericData != null && numericData.getTm() != null) {
                    QmsPointNumericDataDto pointData = new QmsPointNumericDataDto();
                    //赋值字段
                    pointData.setTm(numericData.getTm());
                    pointData.setV(numericData.getV());
                    pointData.setQuality(numericData.getQuality());

                    //保持为null字段
                    pointData.setBeginv(null);
                    pointData.setEndv(null);
                    pointData.setMaxv(null);
                    pointData.setMaxtm(null);
                    pointData.setMinv(null);
                    pointData.setMintm(null);
                    pointData.setUptm(null);

                    pointDatas.add(pointData);
                }
            }

            if (pointDatas.size() > 0x0)
                return pointDatas;
        }

        return null;
    }

    /**
     * 保存通过数据同步系统来的点号数据集合
     *
     * @param pointid 点号编码
     * @param tmseg   点号时段类型
     * @param datas   点号数据集合
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto
     * @author QuCheng Tech
     * @since 2025/10/17
     */
    public QmsPointDataManageResultDto saveDataSyncPointDatas(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericDataDto> datas) {
        //数据同步也可以是人工触发
        return savePointDatas(pointid, tmseg, datas, QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC, null, null, false, null);
    }

    /**
     * 保存点号数据，提供给系统自动计算、人工录入或修改、系统同步的点号数据进行保存
     * 统一在此方法中记录点号数据保存流程日志、发送点号数据消息
     * 此方法中不对点号数据属性进行任何判断和修改（包括数据值、特征值、数据质量），也不对数据时标有效性进行验证
     * 此方法进行数据保存时，会根据点号数据的更新时间uptm进行验证，如果已有数据更新时间＞本次的不进行更新；更新时间验证后再根据数据质量判断是否进行更新；
     *
     * @param pointid    点号编码
     * @param tmseg      点号时段类型
     * @param datas      点号数据集合，每个数据包含完整信息，包含数据质量和uptm
     * @param optype     操作类型；系统自动操作：原始数据触发计算、定时点号数据计算，人工操作：重新处理原始数据、重新点号数据计算、人工录入/修改点号数据
     * @param flowid     流程全局ID
     * @param fromNodeId 流程上一步节点ID
     * @param isManual   是否为人工操作，为false时表示数据为系统自动计算数据
     * @param userName   为人工操作时的操作用户名称
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto
     * @author QuCheng Tech
     * @since 2025/10/13
     */
    public QmsPointDataManageResultDto savePointDatas(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericDataDto> datas,
                                                      QmsDataFlowOperateTypeEnum optype, String flowid, String fromNodeId, boolean isManual, String userName) {
        logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，人工操作[" + isManual
                + "]，流程ID[" + flowid + "]，来源节点ID[" + fromNodeId + "]，数据条数共[" + ((datas != null && datas.size() > 0x0) ? datas.size() : 0x0) + "]条");
        LocalDateTime now = LocalDateTimeUtil.getNow();

        //统一根据optype判断是哪种点号数据保存：系统自动计算出的点号数据保存、人工录入/修改的点号数据保存、数据同步的点号数据保存
        //optype传入的是数据来源的操作类型：原始数据计算、定时点号数据计算等      
        //本次进行点号数据保存操作的具体操作类型，默认为系统自动计算点号数据保存
        QmsDataFlowOperateTypeEnum savePointDataOptype = QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_SYS_CALC;
        //如果是人工操作并且传入的optype是POINT_SAVE_MANUAL（人工录入/修改数据），设置保存点号数据的操作类型为POINT_SAVE_MANUAL（人工录入/修改数据）
        if (isManual == true && optype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY)
            savePointDataOptype = QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY;
            //如果传入的optype是POINT_SAVE_SYNC数据同步（不管是否为人工触发），设置保存点号数据的操作类型为POINT_SAVE_SYNC数据同步
        else if (optype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC)
            savePointDataOptype = QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC;

        //保存点号数据的流程日志
        String saveNodeId = IdentifierUtil.simpleUUID();
        //如果全局流程ID为空，设置为当前保存节点ID；此时为人工进行数据录入或修改，此时是全局流程的开始
        if (StringUtils.isBlank(flowid))
            flowid = saveNodeId;

        //初始化返回结果
        QmsPointDataManageResultDto result = new QmsPointDataManageResultDto();
        result.setSuccess(false);
        result.setAffectCount(0x0);

        //验证输入是否有效：点号必须存在，数据不能为空
        QmsPoint point = null;
        if (StringUtils.isBlank(pointid)) {
            result.setMsg("保存点号数据时点号不能为空");
        } else {
            //判断点号是否存在
            point = basicConfigCacheGetService.getPoint(pointid);
            if (point == null) {
                result.setMsg("保存点号数据时根据点号ID获取不到有效的点号对象");
            }

            //判断输入的数据是否为空
            if (datas == null || datas.size() < 0x1) {
                result.setMsg("保存点号数据时点号数据集合不能为空");
            }
        }

        //如果验证不通过不进行点号数据保存，进行数据流程日志记录
        if (StringUtils.isNotBlank(result.getMsg())) {
            logger.warn("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，人工操作[" + isManual
                    + "]，流程ID[" + flowid + "]，来源节点ID[" + fromNodeId + "]，" + result.getMsg());
            //进行日志记录，只有保存数据操作类型为人工录入/修改数据时设置是否为人工操作为true
            dataFlowLogProducer.send(saveNodeId, saveNodeId, fromNodeId, savePointDataOptype, savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY,
                    now, null, false, result.getMsg(), pointid, tmseg, null, null);

            return result;
        }

        //需要根据点号获取监测要素分辨力（用于判断数值是否一致）、监测要素和点号上下限值（用于自动判断数据质量是否为可疑数据）
        Double pointMaxv = point.getMaxv();
        Double pointMinv = point.getMinv();
        boolean pointAmendMaxv = false;
        boolean pointAmendMinv = false;
        if (point.getMaxvamend() == true)
            pointAmendMaxv = true;
        if (point.getMinvamend() == true)
            pointAmendMinv = true;
        Double resolution = null;
        Double elementMaxv = null;
        Double elementMinv = null;
        boolean elemenAmendMaxv = false;
        boolean elemenAmendMinv = false;
        QmsElement element = basicConfigCacheGetService.getElement(point.getElecode());
        if (element != null) {
            resolution = element.getResolution();
            elementMaxv = element.getMaxv();
            elementMinv = element.getMinv();
            if (element.getMaxvamend() == true)
                elemenAmendMaxv = true;
            if (element.getMinvamend() == true)
                elemenAmendMinv = true;
        }
        logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                + "]，点号中设置的有效值范围为[" + pointMinv + "]-[" + pointMaxv
                + "]，监测要素中设置的有效值范围为[" + elementMinv + "]-[" + elementMaxv + "]，最小分辨力[" + resolution + "]");

        //对点号数据时标有效性进行验证：不能＜当前时间太往前的时间、如果为非未来点号不能＞当前时间太往后的时间
        LocalDateTime currentPeriodTime = QmsTimeUtil.getPeriodTime(now, tmseg);
        LocalDateTime minValidDateTime = null;
        LocalDateTime maxValidDateTime = null;
        //最小有效时间处理方法：如果点号时段类型为日及以下取当前时间往前n天，如果点号时段类型为日以上取当前时段往前n个时段
        if (tmseg == QmsPointTimeSegmentEnum.REAL || tmseg == QmsPointTimeSegmentEnum.MINUTE || tmseg == QmsPointTimeSegmentEnum.HOUR || tmseg == QmsPointTimeSegmentEnum.DAY)
            minValidDateTime = currentPeriodTime.minusDays(QmsConstant.NO_MORE_THAN_DAY_PERIOD_MAX_BACKWARD_DAYS);
        else if (tmseg == QmsPointTimeSegmentEnum.YEAR)
            minValidDateTime = currentPeriodTime.minusYears(QmsConstant.YEAR_PERIOD_MAX_BACKWARD_PERIODS);
        else
            minValidDateTime = QmsTimeUtil.getIntervalPeriodTime(currentPeriodTime, tmseg, -QmsConstant.MORE_THAN_DAY_PERIOD_MAX_BACKWARD_PERIODS);
        //最大有效时间处理方法：如果点号是未来数据为true保持为null，否则实时和分钟点号取当前时间往后n分钟、日及以上时段取当前时间所在时段时间
        if (point.getIsfuture() == null || point.getIsfuture().booleanValue() == false) {
            //为null或为false均当做false，表示不是未来数据点号
            if (tmseg == QmsPointTimeSegmentEnum.REAL || tmseg == QmsPointTimeSegmentEnum.MINUTE)
                maxValidDateTime = currentPeriodTime.plusMinutes(QmsConstant.REAL_POINT_DATA_MAX_FORWARD_MINUTES);
            else
                maxValidDateTime = currentPeriodTime;
        }

        //保存完成后获取实际受影响的点号数据集合，发送至消息队列进行后续的处理
        List<QmsPointDataSaveAffectDto> saveAffectPointDatas = new ArrayList<>();
        //保存点号数据错误的数据时标和对应的错误信息
        Map<LocalDateTime, String> saveFails = new LinkedHashMap<>();
        //记录各新增、修改的总数及成功数量
        int addCount = 0x0;
        int addSuccessCount = 0x0;
        int updateCount = 0x0;
        int updateSuccessCount = 0x0;
        LocalDateTime saveAffectDataBeginTime = null;
        LocalDateTime saveAffectDataEndTime = null;
        //实际受影响的数据是否有至少1条正常数据
        boolean hasAnyNormalData = false;
        //数据时标最大的有效数据，用于后面更新点号数据最新状态
        QmsPointNumericDataDto maxTimeValidPointData = null;

        //对传入的数值数据按时标进行排序，按照点号数据时间先后顺序逐条处理保存
        //List<QmsPointNumericSaveDataDto> sortedByTmDatas=datas.stream().sorted()
        for (QmsPointNumericDataDto pointData : datas) {
            /**
             * 每条点号数据保存的处理步骤
             * 1.数据时标规整：如果根据点号时段类型得到的时标不是当前数据时标，自动进行修正数据时标
             * 2.数据时标有效性判定：不是人工操作触发导致的点号数据保存（不管是系统自动计算、人工录入/修改、数据同步）均需要进行判定
             * 3.数据值value保留精度处理：根据最小分辨力保留精度，避免后面判断数据值是否变化时由于精度保留不一致判定错误
             * 4.特征值统计赋值：保存操作不是数据同步均需要进行特征值统计赋值
             * 5.数据值value根据上限值判断是否为可疑数据处理：根据点号、监测要素中设置的最大和最小值判断是否为可疑数据，判断为可疑数据的修改数据质量
             * 6.数据值防误处理：根据点号数据告警中的有数据防误处理的对数据值(value、beginv、endv)进行判定处理，防误处理不修改数据质量
             * 数据不合理判别：不在保存时处理；如果只是判断是否合理不进行防误处理的在点号数据保存后通过消息触发（可能需要获取前面的数据或其他点号数据计算变幅等）
             * 7.判断是否操作DB：insert判断是否已有相同时标数据、update 判断是否数据值或数据质量有至少一个不同
             * 8.实际有点号数据insert或update后的操作：有触发告警时写入告警记录
             *
             */

            if (pointData.getTm() != null) {
                //基本验证：数据时标不能为空，其他任何值均可以为空(可以是计算出来的数据值为null或NaN)

                //每条点号数据保存的处理步骤 1:数据时标规整
                //根据时段类型对时间再做一次规整，避免出现小时数据中分钟不为0的情况，解决人工录入数据时选择时间与时段类型不匹配的问题
                LocalDateTime time = QmsTimeUtil.getPeriodTime(pointData.getTm(), tmseg);
                if (time.isEqual(pointData.getTm()) == false) {
                    logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                            + "]，数据时标[" + LocalDateTimeUtil.formatDateTime(pointData.getTm())
                            + "]，不在时段起始时间点上，自动修正为[" + LocalDateTimeUtil.formatDateTime(time) + "]");
                }

                //每条点号数据保存的处理步骤 2:数据时标有效性判定
                //在系统自动计算出的点号数据保存时判断数据时标是否有效，如果是人工操作触发计算或保存的不判断数据时标是否有效
                if (isManual == false) {
                    if (minValidDateTime != null && time.isBefore(minValidDateTime) == true) {
                        //数据时标＜最小有效时间
                        String errorMsg = "点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + LocalDateTimeUtil.formatDateTime(time) + "]＜最小有效时间[" + LocalDateTimeUtil.formatDateTime(minValidDateTime)
                                + "]，判定为数据时标错误，不保存该数据";

                        logger.warn("savePointNumericDatas 保存点号数据，" + errorMsg);
                        saveFails.put(time, errorMsg);

                        continue;
                    }
                    if (maxValidDateTime != null && time.isAfter(maxValidDateTime) == true) {
                        //数据时标＞最大有效时间
                        String errorMsg = "点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + LocalDateTimeUtil.formatDateTime(time) + "]＞最大有效时间[" + LocalDateTimeUtil.formatDateTime(maxValidDateTime)
                                + "]，判定为数据时标错误，不保存该数据";

                        logger.warn("savePointNumericDatas 保存点号数据，" + errorMsg);
                        saveFails.put(time, errorMsg);

                        continue;
                    }
                }

                //每条点号数据保存的处理步骤 3:数据值value保留精度处理
                // 判断数据值，此方法为人工或系统自动计算数据的保存，所以要求数据值必须有，没有数据值或数据值错误的不进行保存处理
                //计算的数据值为空或NaN也需要保存；比如之前已有该时间的数据，对计算使用的数据进行删除或修改标记后触发当前指标的重新计算，此时计算初的结果数值为null
                //数据值对精度保留处理，按最小分辨力保留到指定精度
                Double value = QmsNumericUtil.getScaleValue(pointData.getV(), resolution);
                //Double value = QmsNumericUtil.getScaleValue(numericData.getV(), 1.0);//测试使用
                pointData.setV(value);
                logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                        + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + pointData.getV() + "]，保留精度后数据值[" + value + "]");

                //每条点号数据保存的处理步骤 4:特征值统计赋值
                if (savePointDataOptype != QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC) {
                    //本次保存点号数据操作是保存系统自动计算数据或人工录入/修改数据时需要进行特征值字段统计赋值
                    //查询小一个时段点号数据进行时段初末和最值的统计，对统计的特征值也进行精度保留处理
                    setStatisticsFieldValue(point, tmseg, pointData, resolution);
                }

                //每条点号数据保存的处理步骤 5:数据值value根据上限值判断是否需要对数据值进行修订，如果修订设置数据质量为修订数据，如果不修订设置数据质量为可疑数据处理
                //记录数据值超限的提示信息，后面保存数据后写入告警记录时需要
                String dataValueTransfiniteInfo = null;
                QmsPointDataQualityEnum quality = pointData.getQuality();
                if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC) {
                    //通过数据同步的数据值不判断超限，不对数据质量进行任何判定和修改
                } else {
                    //系统自动计算出的点号数据或人工录入/修改的点号数据均判断是否超限
                    //2种数据处理的区别：系统自动计算的点号数据如果超限标记为可疑数据进行保存，人工录入/修改的点号数据如果超限返回错误提示不进行保存

                    //如果是人工录入/修改点号数据保存先设置数据质量为人工数据
                    if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY)
                        quality = QmsPointDataQualityEnum.MANUAL;

                    //如果数据质量是有效数据时根据数据数值判断是否为可疑数据，其他数据质量不需要判断
                    //只需要判断数据质量是有效数据的（正常数据、人工数据），测试数据不作为有效数据不需要判断，可疑数据本身已为可疑数据也不需要重复判断
                    if (quality == QmsPointDataQualityEnum.NORMAL || quality == QmsPointDataQualityEnum.MANUAL) {
                        if (value != null && value.isNaN() == false && value.isInfinite() == false) {
                            //判断是否有设置上下限，如果有设置判断是否超上下限
                            //先判断是否超过点号中的上下限值
                            if (pointMaxv != null && pointMaxv.isNaN() == false && value > pointMaxv) {
                                //数值＞点号中的最大值
                                dataValueTransfiniteInfo = "数据值[" + value + "]＞点号最大值[" + pointMaxv + "]";
                                if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                                    saveFails.put(time, "数据值错误，超出有效范围，不进行数据保存。" + dataValueTransfiniteInfo);

                                    //如果是人工录入/修改数据时数据值超限本条数据不进行保存
                                    continue;
                                } else {
                                    //如果是系统自动计算出的点号数据保存时数据超限，判断是否自动对数据值进行修订，并在告警记录中进行记录（不在此处写入告警记录，因为数据是否最终保存到DB还不确定）
                                    if (pointAmendMaxv == true) {
                                        //需要自动对数据值进行修订，修订后设置数据质量为修订数据
                                        value = pointMaxv;
                                        quality = QmsPointDataQualityEnum.AMEND;
                                        logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＞点号最大值[" + pointMaxv
                                                + "]，使用点号最大值对数据值进行修订，设置数据质量为[" + quality + "]");
                                    } else {
                                        //不需要对数据值进行修订，设置数据质量为可疑数据
                                        quality = QmsPointDataQualityEnum.QUESTION;
                                        logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＞点号最大值[" + pointMaxv
                                                + "]，设置数据质量为[" + quality + "]");
                                    }
                                }
                            } else {
                                if (pointMinv != null && pointMinv.isNaN() == false && value < pointMinv) {
                                    //数值＜点号中的最小值
                                    dataValueTransfiniteInfo = "数据值[" + value + "]＜点号最小值[" + pointMinv + "]";
                                    if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                                        saveFails.put(time, "数据值错误，超出有效范围，不进行数据保存。" + dataValueTransfiniteInfo);

                                        //如果是人工录入/修改数据时数据值超限本条数据不进行保存
                                        continue;
                                    } else {
                                        //如果是系统自动计算出的点号数据保存时数据超限，判断是否自动对数据值进行修订，并在告警记录中进行记录（不在此处写入告警记录，因为数据是否最终保存到DB还不确定）
                                        if (pointAmendMinv == true) {
                                            //需要自动对数据值进行修订，修订后设置数据质量为修订数据
                                            value = pointMinv;
                                            quality = QmsPointDataQualityEnum.AMEND;
                                            logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                    + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＜点号最小值[" + pointMinv
                                                    + "]，使用点号最小值对数据值进行修订，设置数据质量为[" + quality + "]");
                                        } else {
                                            //不需要对数据值进行修订，设置数据质量为可疑数据
                                            quality = QmsPointDataQualityEnum.QUESTION;
                                            logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                    + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＜点号最小值[" + pointMinv
                                                    + "]，设置数据质量为[" + quality + "]");
                                        }
                                    }
                                } else {
                                    if (elementMaxv != null && elementMaxv.isNaN() == false && value > elementMaxv) {
                                        //数值＞监测要素中的最大值
                                        dataValueTransfiniteInfo = "数据值[" + value + "]＞监测要素最大值[" + elementMaxv + "]";
                                        if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                                            saveFails.put(time, "数据值错误，超出有效范围，不进行数据保存。" + dataValueTransfiniteInfo);

                                            //如果是人工录入/修改数据时数据值超限本条数据不进行保存
                                            continue;
                                        } else {
                                            //如果是系统自动计算出的点号数据保存时数据超限，判断是否自动对数据值进行修订，并在告警记录中进行记录（不在此处写入告警记录，因为数据是否最终保存到DB还不确定）
                                            if (elemenAmendMaxv == true) {
                                                //需要自动对数据值进行修订，修订后设置数据质量为修订数据
                                                value = elementMaxv;
                                                quality = QmsPointDataQualityEnum.AMEND;
                                                logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                        + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＞监测要素最大值[" + elementMaxv
                                                        + "]，使用监测要素最大值对数据值进行修订，设置数据质量为[" + quality + "]");
                                            } else {
                                                //不需要对数据值进行修订，设置数据质量为可疑数据
                                                quality = QmsPointDataQualityEnum.QUESTION;
                                                logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                        + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＞监测要素最大值[" + elementMaxv
                                                        + "]，设置数据质量为[" + quality + "]");
                                            }
                                        }
                                    } else {
                                        if (elementMinv != null && elementMinv.isNaN() == false && value < elementMinv) {
                                            //数值＜监测要素中的最小值
                                            dataValueTransfiniteInfo = "数据值[" + value + "]＜监测要素最小值[" + elementMinv + "]";
                                            if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                                                saveFails.put(time, "数据值错误，超出有效范围，不进行数据保存。" + dataValueTransfiniteInfo);

                                                //如果是人工录入/修改数据时数据值超限本条数据不进行保存
                                                continue;
                                            } else {
                                                //如果是系统自动计算出的点号数据保存时数据超限，判断是否自动对数据值进行修订，并在告警记录中进行记录（不在此处写入告警记录，因为数据是否最终保存到DB还不确定）
                                                if (elemenAmendMinv == true) {
                                                    //需要自动对数据值进行修订，修订后设置数据质量为修订数据
                                                    value = elementMaxv;
                                                    quality = QmsPointDataQualityEnum.AMEND;
                                                    logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                            + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＜监测要素最小值[" + elementMinv
                                                            + "]，使用监测要素最小值对数据值进行修订，设置数据质量为[" + quality + "]");
                                                } else {
                                                    //不需要对数据值进行修订，设置数据质量为可疑数据
                                                    quality = QmsPointDataQualityEnum.QUESTION;
                                                    logger.info("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                            + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]＜监测要素最小值[" + elementMinv
                                                            + "]，设置数据质量为[" + quality + "]");
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                //每条点号数据保存的处理步骤 6:数据值防误处理
                //如果判定结果是触发了告警和防误处理，在此处还不能进行告警记录的写入；因为后面判断数据是否需要保存/更新还需要根据数据值和数据质量进行判断，在确定是对数据进行保存/更新后再写入告警记录；
                //先初始化为null，如果后面判断不为null表示进行了防误处理，在数据保存/修改后新增告警记录
                QmsPointDataValueAlarmJudgeResultDto pointDataValueAlarmJudgeResult = null;
                if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC) {
                    //通过数据同步的数据不需要进行防误处理
                } else {
                    //系统自动计算出的点号数据或人工录入/修改的点号数据均需要进行防误处理
                    //2种数据处理的区别：系统自动计算的点号数据如果进行防误处理后对数据进行保存（对数据值进行修改、对数据质量标记设置为正常数据），人工录入/修改的点号数据如果进行防误处理返回错误提示不进行保存
                    //如果在上一步根据点号或者监测要素中的有效值范围判定为可疑数据的，可疑数据当做错误数据（后面会写入告警记录），不需要对可以数据进行防误处理
                    //只需要对数据质量为有效数据的进行防误处理，人工数据也需要进行防误处理（人工录入或修改的数据也可能前后不一致）
                    //只有进行防误处理对数据值进行修改才需要设置数据质量为正常数据，触发告警但未进行防误处理的无法判断数据是否正确保持现有数据质量不变
                    if (quality.getIndex() >= QmsConstant.POINT_DATA_VALID_QUALITY_FROM.getIndex()) {
                        //根据点号+时段类型判断是否有防误处理的告警规则，传入本条点号数据的全部信息（可能还是要使用value值或beginv、endv进行判断）
                        QmsPointDataValueAlarmJudgeResultDto alarmErrorProofJudgeResult = pointDataValueAlarmJudgeService.judgeErrorProof(pointid, tmseg, savePointDataOptype, pointData);
                        //在数据保存前判定的告警只需要处理触发防误处理的告警
                        if (alarmErrorProofJudgeResult != null && alarmErrorProofJudgeResult.isHasTriggerErrorProof() == true) {
                            pointDataValueAlarmJudgeResult = alarmErrorProofJudgeResult;
                            //记录如果是人工录入/修改点号数据并且value数值触发告警防误处理，此时不对点号数据进行保存，返回前端提示信息
                            boolean isManualDataHasErrorProof = false;
                            //防误处理可以对数据值和数据质量标记进行修改赋值，需要遍历判定结果中的逐条告警规则有触发防误处理的获取对应设置的数据值
                            //触发防误处理的数据质量是否需要重新赋值？？防误处理后固定当前点号数据质量为有效数据
                            for (QmsPointDataValueAlarmRuleJudgeResultDto ruleJudgeResult : alarmErrorProofJudgeResult.getRuleJudgeResults()) {
                                if (ruleJudgeResult != null && ruleJudgeResult.isHasTriggerErrorProof() == true) {
                                    //根据防误处理的数据字段进行赋值，此处不管防误处理后赋值的数据值是否有效
                                    //防误处理的数据值不需要再进行精度保留处理，因为赋值使用的数值已经是之前进行精度保留处理后存入数据库的数值
                                    if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.VALUE) {
                                        //如果是人工录入数据是否也进行防误处理？
                                        if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                                            //如果是人工录入/修改数据要触发防误处理的，判定为人工输入的数据错误，返回前端进行提示
                                            isManualDataHasErrorProof = true;
                                            saveFails.put(time, "数据值错误，不进行数据保存。触发异常数据防误处理，防误处理信息[" + ruleJudgeResult.getErrorProofSetMsg() + "]");
                                        } else if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_SYS_CALC) {
                                            logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                    + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]，触发数值不合理判别防误处理，将数据值自动修订为[" + ruleJudgeResult.getErrorProofSetDataValue() +
                                                    "]，触发规则ID[" + ruleJudgeResult.getAlarmRuleId() + "]，规则名称[" + ruleJudgeResult.getAlarmRuleName() + "]");
                                            //系统自动计算数据触发防误处理时自动设置，并设置数据质量为正常数据
                                            //数据质量的设置只对数据值value字段修订时才设置，时段初和时段末值修订保存原有的数据质量不变
                                            value = ruleJudgeResult.getErrorProofSetDataValue();
                                            quality = QmsPointDataQualityEnum.NORMAL;
                                        }
                                    } else if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.BEGIN_VALUE) {
                                        logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，时段初值[" + pointData.getBeginv() + "]，触发数值不合理判别防误处理，将时段初值自动修订为[" + ruleJudgeResult.getErrorProofSetDataValue() +
                                                "]，触发规则ID[" + ruleJudgeResult.getAlarmRuleId() + "]，规则名称[" + ruleJudgeResult.getAlarmRuleName() + "]");
                                        //人工录入/修改数据时只对value字段值进行修改，时段初和时段末值仍由本方法内根据系统中的数据进行统计，因此时段初和时段末值触发防误处理时不进行提示
                                        pointData.setBeginv(ruleJudgeResult.getErrorProofSetDataValue());
                                    } else if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.END_VALUE) {
                                        logger.warn("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，时段末值[" + pointData.getEndv() + "]，触发数值不合理判别防误处理，将时段末值自动修订为[" + ruleJudgeResult.getErrorProofSetDataValue() +
                                                "]，触发规则ID[" + ruleJudgeResult.getAlarmRuleId() + "]，规则名称[" + ruleJudgeResult.getAlarmRuleName() + "]");
                                        //人工录入/修改数据时只对value字段值进行修改，时段初和时段末值仍由本方法内根据系统中的数据进行统计，因此时段初和时段末值触发防误处理时不进行提示
                                        pointData.setEndv(ruleJudgeResult.getErrorProofSetDataValue());
                                    }
                                }
                            }

                            //所有告警规则判断完成后，如果是人工录入/修改数据并且触发防误处理，不对点号数据进行保存，返回前端提示信息
                            if (isManualDataHasErrorProof == true)
                                continue;
                        }
                    }
                }

                //数据值value根据上下限值判断处理及告警防误处理后将数据值和数据质量赋给给点号数据对象
                pointData.setV(value);
                pointData.setQuality(quality);

                //初始化当前时间点号数据保存受影响结果DTO
                QmsPointDataSaveAffectDto saveAffectData = new QmsPointDataSaveAffectDto();
                saveAffectData.setData(pointData);
                saveAffectData.setOps(0x0);//先默认保存受影响类型值为0，表示新增数据

                //每条点号数据保存的处理步骤 7：判断是否操作DB
                LocalDateTime uptm = pointData.getUptm();
                //标识实际是否有对数据库中的点号数据进行insert或update，需要确认有数据写入/更新时进行其他处理
                boolean saveHasAffectData = false;
                //获取当前时标的已有数据
                QmsPointNumericDataDto existData = pointDataQueryService.get(pointid, tmseg, time);
                if (existData == null) {
                    logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                            + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]，不存在已有数据本次进行新增数据");
                    //不存在已有数据，本次为新增
                    addCount++;
                    //新增点号数据，写入数据库
                    String addPointDataResult = this.add(pointid, tmseg, pointData);
                    if (StringUtils.isNotBlank(addPointDataResult)) {
                        logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]，新增数据失败，" + addPointDataResult);
                        //新增点号数据失败，获取失败结果信息
                        saveFails.put(time, addPointDataResult);
                    } else {
                        logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]，新增数据成功");
                        //新增点号数据成功
                        saveHasAffectData = true;
                        saveAffectPointDatas.add(saveAffectData);
                        addSuccessCount++;
                        //更新本次保存实际受影响的起止数据时标
                        if (saveAffectDataBeginTime == null || saveAffectDataBeginTime.isAfter(time))
                            saveAffectDataBeginTime = time;
                        if (saveAffectDataEndTime == null || saveAffectDataEndTime.isBefore(time))
                            saveAffectDataEndTime = time;
                        //如果最终保存的数据质量时正常数据，设置有任意一条数据为正常数据标记为true
                        if (quality == QmsPointDataQualityEnum.NORMAL)
                            hasAnyNormalData = true;
                        //判断数据质量如果有系统中设定的有效数据，并且当前数据时标＞已记录的数据时标时进行更新，用于后面对点号最新状态进行更新使用
                        if (quality.getIndex() >= QmsConstant.POINT_DATA_VALID_QUALITY_FROM.getIndex()) {
                            if (maxTimeValidPointData == null || maxTimeValidPointData.getTm().isAfter(time) == false)
                                maxTimeValidPointData = pointData;
                        }
                    }
                } else {
                    saveAffectData.setExistData(existData);
                    //存在已有数据时，先判断本次数据的更新时间是否有，如果没有再判断数据质量
                    if (existData.getUptm() != null && existData.getUptm().isAfter(uptm) == true) {
                        //已有数据的更新时间＞本次数据的更新时间，不覆盖已有数据
                        if (savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                            saveFails.put(time, "本次数据更新时间[" + LocalDateTimeUtil.formatDateTime(uptm) + "]＜已有数据更新时间[" + LocalDateTimeUtil.formatDateTime(existData.getUptm())
                                    + "]，判定已有数据为新数据，不需要修改已有数据");

                            //如果是人工录入/修改数据时已有数据更新时间＞本次操作更新时间本条数据不进行保存
                            continue;
                        } else {
                            logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                    + "]，数据更新时间[" + LocalDateTimeUtil.formatDateTime(uptm) + "]＜已有数据更新时间[" + LocalDateTimeUtil.formatDateTime(existData.getUptm())
                                    + "]，判定已有数据为新数据，不需要修改已有数据");
                        }
                    } else {
                        //存在已有数据，判断是否要进行更新；当前数据质量≥已有数据质量才进行更新
                        if (QmsPointDataQualityEnum.judgeNeedUpdateData(quality, existData.getQuality()) == true) {
                            int saveAffectOps = 0x0;//先默认保存受影响类型值为0
                            //判断要更新哪些
                            if (QmsNumericUtil.isEqual(pointData.getV(), existData.getV(), resolution) == false) {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，数据值[" + pointData.getV() + "]，已有数据值[" + existData.getV() + "]，判定为值不相等需要修改");
                                saveAffectOps += QmsPointDataSaveAffectEnum.VALUE.getIndex();
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，数据值[" + pointData.getV() + "]，已有数据值[" + existData.getV() + "]，判定数值相等，不需要修改");
                            }
                            if (QmsNumericUtil.isEqual(pointData.getBeginv(), existData.getBeginv(), resolution) == false) {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，时段初值[" + pointData.getBeginv() + "]，已有数据时段初值[" + existData.getBeginv() + "]，判定为值不相等需要修改");
                                saveAffectOps += QmsPointDataSaveAffectEnum.BEGINV.getIndex();
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，时段初值[" + pointData.getBeginv() + "]，已有数据时段初值[" + existData.getBeginv() + "]，判定数值相等，不需要修改");
                            }
                            if (QmsNumericUtil.isEqual(pointData.getEndv(), existData.getEndv(), resolution) == false) {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，时段末值[" + pointData.getEndv() + "]，已有数据时段末值[" + existData.getEndv() + "]，判定为值不相等需要修改");
                                saveAffectOps += QmsPointDataSaveAffectEnum.ENDV.getIndex();
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，时段末值[" + pointData.getEndv() + "]，已有数据时段末值[" + existData.getEndv() + "]，判定数值相等，不需要修改");
                            }
                            if (QmsNumericUtil.isEqual(pointData.getMaxv(), existData.getMaxv(), resolution) == false) {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，最大值[" + pointData.getMaxv() + "]，已有数据最大值[" + existData.getMaxv() + "]，判定为值不相等需要修改");
                                //最值的更新还需要判断最值时间是否变化
                                saveAffectOps += QmsPointDataSaveAffectEnum.MAX.getIndex();
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，最大值[" + pointData.getMaxv() + "]，已有数据最大值[" + existData.getMaxv() + "]，判定数值相等，不需要修改");
                            }
                            if (QmsNumericUtil.isEqual(pointData.getMinv(), existData.getMinv(), resolution) == false) {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，最小值[" + pointData.getMinv() + "]，已有数据最小值[" + existData.getMinv() + "]，判定为值不相等需要修改");
                                saveAffectOps += QmsPointDataSaveAffectEnum.MIN.getIndex();
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，最小值[" + pointData.getMinv() + "]，已有数据最小值[" + existData.getMinv() + "]，判定数值相等，不需要修改");
                            }
                            //判断数据质量是否变化，只需要判断本次数据质量＞已有数据质量即可；不大于已有数据质量时不进行更新
                            //2025-9-30，数据质量级别即使没有或者本次数据质量级别＜已有数据级别也需要更新；只需要判断2个质量级别是否相等，不相等时需要更新数据质量
                            //if (quality.getIndex() > existData.getQuality().getIndex()) {
                            if (quality.getIndex() != existData.getQuality().getIndex()) {
                                saveAffectOps += QmsPointDataSaveAffectEnum.QUALITY.getIndex();
                            }

                            if (saveAffectOps > 0x0) {
                                //判断如果有需要更新的数据值字段（或者数据质量）才进行点号数据更新
                                updateCount++;
                                saveAffectData.setOps(saveAffectOps);
                                //调用点号数据更新方法，获取更新结果
                                String updatePointDataResult = this.update(pointid, tmseg, pointData, saveAffectOps, existData);
                                if (StringUtils.isNotBlank(updatePointDataResult)) {
                                    logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                            + "]，数据值[" + value + "]，修改已有数据失败，" + updatePointDataResult);
                                    //修改点号数据失败，获取失败结果信息
                                    saveFails.put(time, updatePointDataResult);
                                } else {
                                    logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                            + "]，数据值[" + value + "]，修改已有数据成功");
                                    //修改点号数据成功
                                    saveHasAffectData = true;
                                    saveAffectPointDatas.add(saveAffectData);
                                    updateSuccessCount++;
                                    //更新本次保存实际受影响的起止数据时标
                                    if (saveAffectDataBeginTime == null || saveAffectDataBeginTime.isAfter(time))
                                        saveAffectDataBeginTime = time;
                                    if (saveAffectDataEndTime == null || saveAffectDataEndTime.isBefore(time))
                                        saveAffectDataEndTime = time;
                                    //如果最终保存的数据质量时正常数据，设置有任意一条数据为正常数据标记为true
                                    if (quality == QmsPointDataQualityEnum.NORMAL)
                                        hasAnyNormalData = true;
                                    //判断数据质量如果有系统中设定的有效数据，并且当前数据时标＞已记录的数据时标时进行更新，用于后面对点号最新状态进行更新使用
                                    if (quality.getIndex() >= QmsConstant.POINT_DATA_VALID_QUALITY_FROM.getIndex()) {
                                        if (maxTimeValidPointData == null || maxTimeValidPointData.getTm().isAfter(time) == false)
                                            maxTimeValidPointData = pointData;
                                    }
                                }
                            } else {
                                logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                        + "]，数据值[" + value + "]，判定为没有任何字段数据值有变化，不需要修改已有数据");
                            }
                        } else {
                            //根据数据质量判断后不对已有的数据进行更新
                            logger.debug("savePointDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype + "]，数据时标[" + QmsTimeUtil.format(time, tmseg)
                                    + "]，数据值[" + value + "]，数据质量[" + quality + "]，已有数据质量[" + existData.getQuality() + "]，判定为不需要修改已有数据");
                        }
                    }
                }

                //每条点号数据保存的处理步骤 8：实际有点号数据insert或update后的操作
                if (saveHasAffectData == true) {
                    //有可能出现点号或监测要素有效值范围和告警规则防误处理同时触发；如采集的数据错误，超出有效值范围被自动修订，又在告警规则防误处理中再次触发
                    if (StringUtils.isNotBlank(dataValueTransfiniteInfo)) {
                        //如果没有进行防误处理，判断是否有数据值超点号或监测要素的有效值范围，如果有进行新增告警记录
                        logger.debug("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value + "]，确认数据保存到数据库成功，根据有效值范围对数据值进行修订，" + dataValueTransfiniteInfo);
                    }
                    if (pointDataValueAlarmJudgeResult != null) {
                        //如果当前点号数据有触发防误处理，在数据保存成功后新增告警记录
                        logger.debug("savePointNumericDatas 保存点号数据，点号[" + pointid + "]，时段类型[" + tmseg + "]，触发流程操作类型[" + optype
                                + "]，数据时标[" + QmsTimeUtil.format(time, tmseg) + "]，数据值[" + value +
                                "]，确认数据保存到数据库成功，根据数据不合理判别防误处理进行修订，触发防误处理规则数量[" + pointDataValueAlarmJudgeResult.getTriggerErrorProofRuleCount() + "]");
                    }
                }
            }
        }

        //保存失败集合不为空时对结果进行赋值
        if (saveFails.size() > 0x0) {
            result.setFails(saveFails);
        }
        //保存成功受影响的数据集合不为空时对结果进行赋值
        if (saveAffectPointDatas.size() > 0x0) {
            //设置返回的结果成功为true，在有受影响记录时才设置保存数据成功为true，如果受影响记录为0设置数据成功为false
            result.setSuccess(true);
            result.setAffectCount(saveAffectPointDatas.size());
            result.setAffectDatas(saveAffectPointDatas);
        }

        //保存点号数据成功后进行流程日志记录，只有保存数据操作类型为人工录入/修改数据时设置是否为人工操作为true
        //流程日志中的结果信息：使用保存操作影响的数据条数+错误提示信息（如有）
        StringBuilder sbSaveDataFlowResult = new StringBuilder();
        sbSaveDataFlowResult.append("本次新增数据[" + addCount + "]条，新增成功数据[" + addSuccessCount + "]条，修改数据[" + updateCount + "]条，修改成功数据[" + updateSuccessCount + "]条");
        if (saveFails.size() > 0x0) {
            for (Map.Entry<LocalDateTime, String> failEntry : saveFails.entrySet()) {
                sbSaveDataFlowResult.append("\n数据时标[" + QmsTimeUtil.format(failEntry.getKey(), tmseg) + "] 失败信息：" + failEntry.getValue());
            }
        }

        dataFlowLogProducer.send(saveNodeId, saveNodeId, fromNodeId, savePointDataOptype, savePointDataOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY,
                now, null, true, sbSaveDataFlowResult.toString(), pointid, tmseg, saveAffectDataBeginTime, saveAffectDataEndTime);

        if (saveAffectPointDatas.size() < 0x1) {
            //最终受影响的点号数据为空，只进行数据流程日志记录，没有需要发送到消息队列中的点号数据消息
            //流程日志消息发送已经在前面执行
        } else {
            //更新点号最新状态
            updatePointDataLatest(pointid, tmseg, null, maxTimeValidPointData);

            //将受影响（新增、修改）的点号数据集合发送至点号数据消息队列中，根据点号计算关联点号计算和点号数据变化日志记录均使用此消息
            QmsMqPointDataMessageDto pointDataMsg = new QmsMqPointDataMessageDto();
            pointDataMsg.setFlowid(flowid);
            //设置当前节点ID为生成的保存数据节点ID
            pointDataMsg.setNodeid(saveNodeId);
            pointDataMsg.setFromnodeid(fromNodeId);
            pointDataMsg.setPointid(pointid);
            pointDataMsg.setTmseg(tmseg);
            //先设置是否为人工操作为false，只有在确定是人工进行点号数据录入或修改时赋值为true
            pointDataMsg.setManual(false);
            //设置消息中的数据保存操作类型
            pointDataMsg.setOptype(savePointDataOptype);
            //点号数据消息中的数据质量赋值：
            //如果是人工操作触发的数据保存：操作类型为人工录入/修改数据赋值为人工数据，其他操作类型（人工进行原始数据、点号数据计算）均按照非人工操作触发数据保存进行赋值
            //不是人工操作触发的数据保存：有任意一条正常数据赋值为正常数据，全部为可疑数据赋值为可疑数据
            if (isManual == true && optype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_MANUAL_ENTRY) {
                //确定是人工进行点号数据录入或修改
                pointDataMsg.setManual(false);
                //设置操作用户名称
                pointDataMsg.setUserName(userName);
                pointDataMsg.setQuality(QmsPointDataQualityEnum.MANUAL);
            } else {
                //其他均判定为点号数据是通过系统自动计算出的结果，不管系统计算是自动触发还是人工触发
                if (hasAnyNormalData == true)
                    pointDataMsg.setQuality(QmsPointDataQualityEnum.NORMAL);
                else
                    pointDataMsg.setQuality(QmsPointDataQualityEnum.QUESTION);
            }
            //传入实际保存受影响的数据起止时标
            pointDataMsg.setBeginTime(saveAffectDataBeginTime);
            pointDataMsg.setEndTime(saveAffectDataEndTime);
            //设置消息中的实际受影响点号数据集合
            pointDataMsg.setAffectDatas(saveAffectPointDatas);
            //设置成功和失败的数据条数
            pointDataMsg.setAffectCount(addSuccessCount + updateSuccessCount);
            pointDataMsg.setFailCount(saveFails.size());

            //发送点号数据消息
            pointDataProducer.send(pointDataMsg);
        }

        return result;
    }

    /**
     * 设置点号数值数据对象中的统计字段值，根据点号查询小一个时段类型数据进行统计后赋值
     *
     * @param point      点号对象
     * @param tmseg      时段类型
     * @param data       点号数据对象，对此对象中的统计值进行赋值
     * @param resolution 数据值最小分辨力，在对特征值赋值时根据最小分辨力进行精度保留处理
     * @return void
     * @author QuCheng Tech
     * @since 2025/9/29
     */
    void setStatisticsFieldValue(QmsPoint point, QmsPointTimeSegmentEnum tmseg, QmsPointNumericDataDto data, Double resolution) {
        //如果点号信息为空、数值数据DTO为空、时段类型错误不进行提取数据和统计值处理
        if (point != null && tmseg != null && tmseg != QmsPointTimeSegmentEnum.UN_DEFINE && data != null) {
            //取点号的小一个时段类型
            QmsPointTimeSegmentEnum smallerTmseg = QmsPointTimeSegmentEnum.getSmallerOneTimeSegmentEnum(point.getTmsegs(), tmseg);
            //点号的小一个时段类型有效时才进行点号数据获取
            if (smallerTmseg != QmsPointTimeSegmentEnum.UN_DEFINE) {
                LocalDateTime tm = data.getTm();
                //查询小一个时段的数据起止时间段
                //起止时间段应该保持与数值计算中配置的输入点号数据起止时间一致，不能直接使用时段类型获取时间
                //如日雨量（8点为分界统计）：如使用日时段类型获取数据得到的小时/实时雨量为0点~24点数据，根据数值计算中配置的输入数据应使用8点~8点数据
                //另外还有起止时间点是否包含，也应该是根据数值计算中配置的输入数据信息来控制
                //在计算方法关联的输入数据配置中筛选条件：站点关系为同站点、监测要素为同要素、点号时段类型为小一个时段类型
                //2025-9-29，考虑与触发关联点号计算使用同一套处理机制，可以从缓存的点号和触发关联点号计算关系中直接进行查找，如：直接根据点号ID查找日点号关联小时点号的记录
                LocalDateTime queryPointDataBeginTime = QmsTimeUtil.getPeriodTime(tm, tmseg);
                LocalDateTime queryPointDataEndTime = QmsTimeUtil.getNextPeriodTime(queryPointDataBeginTime, tmseg);
                boolean excludeBeginTime = false;
                boolean excludeEndTime = true;
                //如果无法获取计算方法中关联的输入点号配置，使用默认设置；默认设置为根据时段类型自动获取时段的起止时间点，包含起始时间点、不包含截止时间点(小时及以上时段)，指定数据质量为有效数据
                List<QmsPointNumericDataDto> pointRangeDatas = pointDataQueryService.query(point.getPointid(), smallerTmseg, queryPointDataBeginTime, queryPointDataEndTime, excludeBeginTime, excludeEndTime, QmsConstant.POINT_DATA_VALID_QUALITY_FROM);
                //有数据才能统计特征值，没有数据不统计特征值
                if (pointRangeDatas != null && pointRangeDatas.size() > 0x0) {
                    logger.debug("setStatisticsFieldValue 查询用于统计特征值的点号数据共[" + pointRangeDatas.size() + "]条");
                    //根据是否包含起始、截止时间点计算出时段初和时段末的时间
                    //如果不包含起始时间点，时段初时间往后一个时段
                    LocalDateTime hourBeginTime = QmsTimeUtil.getIntervalPeriodTime(queryPointDataBeginTime, smallerTmseg, excludeBeginTime == true ? 0x1 : 0x0);
                    //如果不包含截止时间点，时段末时间往前一个时段
                    LocalDateTime hourEndTime = QmsTimeUtil.getIntervalPeriodTime(queryPointDataEndTime, smallerTmseg, excludeEndTime == true ? -0x1 : 0x0);

                    //获取时间点为时段初时间点的数据，时段初的时间点是否包含已经在前面处理，固定获取hourBeginTime时间点的数据
                    Optional<QmsPointNumericDataDto> beginData = pointRangeDatas.stream().filter(p -> p.getTm().isEqual(hourBeginTime)).findFirst();
                    if (beginData != null && beginData.isPresent()) {
                        if (beginData != null && beginData.isPresent()) {
                            QmsPointNumericDataDto beginNumericData = beginData.get();
                            //如果点号中配置了取对应字段值并且对应字段值有效时使用，其他情况下均取value值
                            if (point.getIsusev() == false && beginNumericData.getBeginv() != null && beginNumericData.getBeginv().isNaN() == false) {
                                logger.debug("setStatisticsFieldValue 统计时段初数据值，根据时段初字段值统计，时段初时间[" + QmsTimeUtil.format(hourBeginTime, smallerTmseg) +
                                        "]，数据时标[" + QmsTimeUtil.format(beginNumericData.getTm(), smallerTmseg) + "]，时段初数据值[" + beginNumericData.getBeginv() + "]");
                                data.setBeginv(QmsNumericUtil.getScaleValue(beginNumericData.getBeginv(), resolution));
                            } else {
                                logger.debug("setStatisticsFieldValue 统计时段初数据值，根据数据值字段统计，时段初时间[" + QmsTimeUtil.format(hourBeginTime, smallerTmseg)
                                        + "]，数据时标[" + QmsTimeUtil.format(beginNumericData.getTm(), smallerTmseg) + "]，数据值[" + beginNumericData.getV() + "]");
                                data.setBeginv(QmsNumericUtil.getScaleValue(beginNumericData.getV(), resolution));
                            }
                        } else {
                            logger.debug("setStatisticsFieldValue 统计时段初数据值，时段初时间[" + QmsTimeUtil.format(hourBeginTime, smallerTmseg) + "]，未找到时段初数据");
                            if (smallerTmseg == QmsPointTimeSegmentEnum.REAL || smallerTmseg == QmsPointTimeSegmentEnum.MINUTE) {
                                //由于实时数据无法判断起始时间点，因此获取距起始时间点最近的一条作为时段初数据
                                List<QmsPointNumericDataDto> noLessThanHourBeinTimeDatas = pointRangeDatas.stream().filter(p -> p.getTm().isAfter(hourBeginTime)).sorted(Comparator.comparing(QmsPointNumericDataDto::getTm)).collect(Collectors.toList());
                                if (noLessThanHourBeinTimeDatas != null && noLessThanHourBeinTimeDatas.size() > 0x0) {
                                    QmsPointNumericDataDto latestData = noLessThanHourBeinTimeDatas.get(0x0);
                                    //如果点号中配置了取对应字段值并且对应字段值有效时使用，其他情况下均取value值
                                    if (point.getIsusev() == false && latestData.getBeginv() != null && latestData.getBeginv().isNaN() == false) {
                                        logger.debug("setStatisticsFieldValue 统计时段初数据值，根据时段初字段值统计，＞时段初时间[" + QmsTimeUtil.format(hourBeginTime, smallerTmseg)
                                                + "]，数据时标[" + QmsTimeUtil.format(latestData.getTm(), smallerTmseg) + "]，时段初数据值[" + latestData.getBeginv() + "]");
                                        data.setBeginv(QmsNumericUtil.getScaleValue(latestData.getBeginv(), resolution));
                                    } else {
                                        logger.debug("setStatisticsFieldValue 统计时段初数据值，根据数据值字段统计，＞时段初时间[" + QmsTimeUtil.format(hourBeginTime, smallerTmseg)
                                                + "]，数据时标[" + QmsTimeUtil.format(latestData.getTm(), smallerTmseg) + "]，数据值[" + latestData.getV() + "]");
                                        data.setBeginv(QmsNumericUtil.getScaleValue(latestData.getV(), resolution));
                                    }
                                }
                            }
                        }
                    }

                    //获取时间点为时段末时间点的数据，时段末的时间点是否包含已经在前面处理，固定获取hourEndTime时间点的数据
                    Optional<QmsPointNumericDataDto> endData = pointRangeDatas.stream().filter(p -> p.getTm().isEqual(hourEndTime)).findFirst();
                    if (endData != null && endData.isPresent()) {
                        QmsPointNumericDataDto endNumericData = endData.get();
                        //如果点号中配置了取对应字段值并且对应字段值有效时使用，其他情况下均取value值
                        if (point.getIsusev() == false && endNumericData.getEndv() != null && endNumericData.getEndv().isNaN() == false) {
                            logger.debug("setStatisticsFieldValue 查找时段末数据，根据时段末字段值统计，时段末时间[" + QmsTimeUtil.format(hourEndTime, smallerTmseg)
                                    + "]，数据时标[" + QmsTimeUtil.format(endNumericData.getTm(), smallerTmseg) + "]，时段末数据值[" + endNumericData.getEndv() + "]");
                            data.setEndv(QmsNumericUtil.getScaleValue(endNumericData.getEndv(), resolution));
                        } else {
                            logger.debug("setStatisticsFieldValue 查找时段末数据，根据数据值字段统计，时段末时间[" + QmsTimeUtil.format(hourEndTime, smallerTmseg)
                                    + "]，数据时标[" + QmsTimeUtil.format(endNumericData.getTm(), smallerTmseg) + "]，数据值[" + endNumericData.getV() + "]");
                            data.setEndv(QmsNumericUtil.getScaleValue(endNumericData.getV(), resolution));
                        }
                    } else {
                        logger.debug("setStatisticsFieldValue 查找时段末数据，时段末时间[" + QmsTimeUtil.format(hourEndTime, smallerTmseg) + "]，未找到时段末数据");
                        if (smallerTmseg == QmsPointTimeSegmentEnum.REAL || smallerTmseg == QmsPointTimeSegmentEnum.MINUTE) {
                            //由于实时数据无法判断截止时间点，因此获取距截止时间点最近的一条作为时段末数据
                            List<QmsPointNumericDataDto> noMoreThanHourEndTimeDatas = pointRangeDatas.stream().filter(p -> p.getTm().isBefore(hourEndTime)).sorted(Comparator.comparing(QmsPointNumericDataDto::getTm).reversed()).collect(Collectors.toList());
                            if (noMoreThanHourEndTimeDatas != null && noMoreThanHourEndTimeDatas.size() > 0x0) {
                                QmsPointNumericDataDto latestData = noMoreThanHourEndTimeDatas.get(0x0);
                                //如果点号中配置了取对应字段值并且对应字段值有效时使用，其他情况下均取value值
                                if (point.getIsusev() == false && latestData.getEndv() != null && latestData.getEndv().isNaN() == false) {
                                    logger.debug("setStatisticsFieldValue 查找时段末数据，根据时段末字段值统计，＜时段末时间[" + QmsTimeUtil.format(hourEndTime, smallerTmseg)
                                            + "]，数据时标[" + QmsTimeUtil.format(latestData.getTm(), smallerTmseg) + "]，时段末数据值[" + latestData.getEndv() + "]");
                                    data.setEndv(QmsNumericUtil.getScaleValue(latestData.getEndv(), resolution));
                                } else {
                                    logger.debug("setStatisticsFieldValue 查找时段末数据，根据数据值字段统计，＜时段末时间[" + QmsTimeUtil.format(hourEndTime, smallerTmseg)
                                            + "]，数据时标[" + QmsTimeUtil.format(latestData.getTm(), smallerTmseg) + "]，数据值[" + latestData.getV() + "]");
                                    data.setEndv(QmsNumericUtil.getScaleValue(latestData.getV(), resolution));
                                }
                            }
                        }
                    }

                    //最值和对应时间处理
                    //如果点号中配置了取对应字段值并且对应字段值有效时使用，其他情况下均取value值
                    //判断是否已经使用对应字段值设置了最值特征值，如果没有默认使用value字段值设置最值特征值
                    boolean hasSetMinAndMax = false;
                    if (point.getIsusev() == false && smallerTmseg != QmsPointTimeSegmentEnum.REAL && smallerTmseg != QmsPointTimeSegmentEnum.MINUTE) {
                        logger.debug("setStatisticsFieldValue 统计最值，根据最小值和最大值字段值统计");
                        //特征值均使用对应字段的数值进行统计
                        //时段初值就使用时段初时间点数据中的beginv，时段末值就使用时段末时间点数据中的endv
                        //最小值使用按minv排序后的第一个数据中的minv和mintm：先按minv属性Asc，再按mintm属性Asc
                        List<QmsPointNumericDataDto> sortedByMinvAscData = pointRangeDatas.stream().sorted(Comparator.comparingDouble(QmsPointNumericDataDto::getMinv).thenComparing(Comparator.comparing(QmsPointNumericDataDto::getMintm))).collect(Collectors.toList());
                        //第一个是最小值
                        QmsPointNumericDataDto minData = sortedByMinvAscData.get(0x0);
                        logger.debug("setStatisticsFieldValue 统计最小值，根据最小值字段统计，最小值数据时标[" + QmsTimeUtil.format(minData.getTm(), smallerTmseg) + "]，最小值[" + minData.getMinv() + "]");
                        if (minData.getMintm() != null && minData.getV() != null && minData.getV().isNaN() == false) {
                            //最小值和最小值时间有效，使用最小值字段设置为统计特征值
                            hasSetMinAndMax = true;

                            data.setMintm(minData.getMintm());
                            data.setMinv(QmsNumericUtil.getScaleValue(minData.getMinv(), resolution));
                        }
                        //最大值使用按maxv排序后的最后一个数据中的maxv和maxtm：先按maxv属性Desc，再按maxtm属性Asc
                        List<QmsPointNumericDataDto> sortedByMaxvDescData = pointRangeDatas.stream().sorted(Comparator.comparingDouble(QmsPointNumericDataDto::getMaxv).reversed().thenComparing(Comparator.comparing(QmsPointNumericDataDto::getMaxtm))).collect(Collectors.toList());
                        QmsPointNumericDataDto maxData = sortedByMaxvDescData.get(0x0);
                        logger.debug("setStatisticsFieldValue 统计最大值，根据最大值字段统计，最大值数据时标[" + QmsTimeUtil.format(maxData.getTm(), smallerTmseg) + "]，最大值[" + maxData.getV() + "]");
                        if (maxData.getMintm() != null && maxData.getV() != null && maxData.getV().isNaN() == false) {
                            //最大值和最大值时间有效，使用最大值字段设置为统计特征值
                            hasSetMinAndMax = true;

                            data.setMaxtm(maxData.getMaxtm());
                            data.setMaxv(QmsNumericUtil.getScaleValue(maxData.getMaxv(), resolution));
                        }
                    }
                    if (hasSetMinAndMax == false) {
                        logger.debug("setStatisticsFieldValue 统计最值，根据数据值字段统计");
                        //如果没有使用最值字段的值设置特征值，默认均使用value字段的值进行最值特征值的统计
                        //过滤value不为null和NaN的记录
                        List<QmsPointNumericDataDto> hasValueDatas = pointRangeDatas.stream().filter(p -> p.getV() != null && p.getV().isNaN() == false).collect(Collectors.toList());
                        if (hasValueDatas != null && hasValueDatas.size() > 0x0) {
                            logger.debug("setStatisticsFieldValue 统计最值，根据数据值字段统计，根据有效数据值过滤后的点号数据共[" + hasValueDatas.size() + "]条");
                            //排序后获取最值：先按value属性Asc，再按tm属性Asc
                            List<QmsPointNumericDataDto> sortedByValueAscData = hasValueDatas.stream().sorted(Comparator.comparingDouble(QmsPointNumericDataDto::getV).thenComparing(Comparator.comparing(QmsPointNumericDataDto::getTm))).collect(Collectors.toList());
                            //第一个是最小值
                            QmsPointNumericDataDto minData = sortedByValueAscData.get(0x0);
                            logger.debug("setStatisticsFieldValue 统计最小值，根据数据值字段统计，最小值数据时标[" + QmsTimeUtil.format(minData.getTm(), smallerTmseg) + "]，最小值[" + minData.getV() + "]");
                            data.setMintm(minData.getTm());
                            data.setMinv(QmsNumericUtil.getScaleValue(minData.getV(), resolution));
                            //最后一个是最大值，直接使用Asc升序排序的获取最后一个会导致在有多个相同的最大值时最大值时间不是第一个最大值数据的时间（而是最后一个最大值数据的时间）
                            //QmsPointNumericDataDto maxData = sortedByValueAscData.get(sortedByValueAscData.size() - 1);
                            //排序后获取最大值：先按value属性Desc，再按tm属性Asc
                            List<QmsPointNumericDataDto> sortedByValueDescData = hasValueDatas.stream().sorted(Comparator.comparingDouble(QmsPointNumericDataDto::getV).reversed().thenComparing(Comparator.comparing(QmsPointNumericDataDto::getTm))).collect(Collectors.toList());
                            QmsPointNumericDataDto maxData = sortedByValueDescData.get(0x0);
                            logger.debug("setStatisticsFieldValue 统计最大值，根据数据值字段统计，最大值数据时标[" + QmsTimeUtil.format(maxData.getTm(), smallerTmseg) + "]，最大值[" + maxData.getV() + "]");
                            data.setMaxtm(maxData.getTm());
                            data.setMaxv(QmsNumericUtil.getScaleValue(maxData.getV(), resolution));
                        } else {
                            logger.debug("setStatisticsFieldValue 统计最值，根据数据值字段统计，根据有效数据值过滤后的点号数据为空，无法进行最值的统计");
                        }
                    }
                }
            }
        }
    }

    /**
     * 人工删除指定点号数据
     *
     * @param pointid   点号
     * @param tmseg     点号时段类型
     * @param beginTime 数据起始时间，自动根据时段类型获取对应时段时间
     * @param endTime   数据截止时间，自动根据时段类型获取对应时段时间
     * @param userName  操作用户名称，用于点号数据日志记录
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto
     * @author QuCheng Tech
     * @since 2025/10/10
     */
    public QmsPointDataManageResultDto delete(String pointid, QmsPointTimeSegmentEnum tmseg, LocalDateTime beginTime, LocalDateTime endTime, String userName) {
        return setQuality(pointid, tmseg, beginTime, endTime, QmsPointDataQualityEnum.DELETE, userName);
    }

    /**
     * 人工设置点号数据数据质量，设置数据质量标记只能是人工操作，并且只能设置为删除、错误或人工数据（正确）3种质量中的一种
     *
     * @param pointid   点号
     * @param tmseg     点号时段类型
     * @param beginTime 数据起始时间，自动根据时段类型获取对应时段时间
     * @param endTime   数据截止时间，自动根据时段类型获取对应时段时间
     * @param quality   设置的数据质量标记，只能设置为删除、错误或人工数据（正确）3种质量中的一种
     * @param userName  操作用户名称，用于点号数据日志记录
     * @return qc.module.qms.dto.pointdata.QmsPointDataManageResultDto
     * @author QuCheng Tech
     * @since 2025/10/10
     */
    public QmsPointDataManageResultDto setQuality(String pointid, QmsPointTimeSegmentEnum tmseg, LocalDateTime beginTime, LocalDateTime endTime, QmsPointDataQualityEnum quality, String userName) {
        LocalDateTime now = LocalDateTimeUtil.getNow();
        //初始化返回结果
        QmsPointDataManageResultDto result = new QmsPointDataManageResultDto();
        result.setSuccess(false);
        result.setAffectCount(0x0);

        //验证输入是否有效：点号编码不能为空，时段类型和时间段必须正确
        QmsPoint point = null;
        if (StringUtils.isBlank(pointid)) {
            result.setMsg("设置点号数据质量时点号不能为空");
        } else {
            //判断点号是否存在
            point = basicConfigCacheGetService.getPoint(pointid);
            if (point == null) {
                result.setMsg("设置点号数据质量时根据点号ID获取不到有效的点号对象");
            } else {
                //要设置的数据质量只能为以下几种：根据要设置的数据质量设置不同的修改条件
                //DELETE：删除数据；修改使用的过滤条件：数据质量＞DELETE(1, "已删除")
                //ERROR：人工判断数据为错误数据；修改使用的过滤条件：数据质量＞ERROR(2, "错误数据")
                //MANUAL：人工判断数据为正确数据；修改使用的过滤条件：数据质量＜MANUAL(9, "人工数据")
                if (quality != QmsPointDataQualityEnum.DELETE || quality != QmsPointDataQualityEnum.ERROR || quality != QmsPointDataQualityEnum.MANUAL)
                    result.setMsg("要设置的数据质量不能为" + quality);
                else {
                    if (tmseg == null || tmseg == QmsPointTimeSegmentEnum.UN_DEFINE) {
                        result.setMsg("设置点号数据质量时点号时段类型不能为空或无效");
                    } else {
                        if (beginTime == null) {
                            result.setMsg("设置点号数据质量时起始时间不能为空");
                        } else {
                            if (endTime == null) {
                                result.setMsg("设置点号数据质量时截止时间不能为空");
                            } else {
                                if (beginTime.isAfter(endTime)) {
                                    result.setMsg("设置点号数据质量时起始时间不能大于截止时间");
                                }
                            }
                        }
                    }
                }
            }
        }

        //如果验证不通过不进行点号数据删除
        if (StringUtils.isNotBlank(result.getMsg())) {
            return result;
        }

        //根据时段类型对输入的起止时间进行规整
        LocalDateTime beginPeriodTime = QmsTimeUtil.getPeriodTime(beginTime, tmseg);
        LocalDateTime endPeriodTime = QmsTimeUtil.getPeriodTime(endTime, tmseg);

        //根据数据质量标记判定操作类型
        QmsDataFlowOperateTypeEnum setPointQualityOptype = QmsDataFlowOperateTypeEnum.UN_DEFINE;
        if (quality == QmsPointDataQualityEnum.DELETE)
            setPointQualityOptype = QmsDataFlowOperateTypeEnum.DELETE_POINT_DATA;
        else if (quality == QmsPointDataQualityEnum.ERROR)
            setPointQualityOptype = QmsDataFlowOperateTypeEnum.SET_POINT_DATA_IS_ERROR;
        else
            setPointQualityOptype = QmsDataFlowOperateTypeEnum.SET_POINT_DATA_IS_RIGHT;

        try {
            //设置点号数据质量时也需要进行告警判断及防误处理，如果是设置点号数据为无效数据（删除、错误数据）不需要在设置数据质量前进行判定（统一由点号数据消息触发）
            //如果是设置点号数据为有效数据（正确数据），在设置前需要对点号数据值进行有效性判定和是否进行防误处理；
            //如果原本数据值无效、但数据质量为错误数据或可疑数据，此时进行设置为有效数据不进行数据值有效性判定会导致数据值是错误的，需要在修改数据质量前进行数据值有效性判断和防误处理
            //初始化一个设置点号数据质量成功受影响的点号数据集合，在确定设置操作成功后进行赋值，后面根据此集合获取设置成功受影响的点号数据集合
            List<QmsPointNumericDataDto> setQualityAffectDatas = new LinkedList<>();
            //设置点号数据质量操作错误的数据时标和对应的错误信息
            Map<LocalDateTime, String> saveFails = new LinkedHashMap<>();
            if (quality == QmsPointDataQualityEnum.MANUAL) {
                //如果本次操作是设置数据为有效数据，先查询到受影响的点号数据，逐条对数据值进行有效性判定和防误处理；如果触发防误处理返回错误信息给前端进行提示
                List<QmsPointNumericDataDto> needSetQualityDatas = null;
                //根据不同的点号时段类型调用不同的service进行数据查询
                switch (tmseg) {
                    case REAL:
                    case MINUTE:
                        List<QmsPointRealNumericData> realEns = pointRealNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (realEns != null && realEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.realEntitiesToDtoPointList(realEns);
                        break;
                    case HOUR:
                        List<QmsPointHourNumericData> hourEns = pointHourNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (hourEns != null && hourEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.hourEntitiesToDtoList(hourEns);
                        break;
                    case DAY:
                        List<QmsPointDayNumericData> dayEns = pointDayNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (dayEns != null && dayEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.dayEntitiesToDtoList(dayEns);
                        break;
                    case WEEK:
                        List<QmsPointWeekNumericData> weekEns = pointWeekNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (weekEns != null && weekEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.weekEntitiesToDtoList(weekEns);
                        break;
                    case TEN_DAYS:
                        List<QmsPointTendaysNumericData> tendaysEns = pointTendaysNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (tendaysEns != null && tendaysEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.tendaysEntitiesToDtoList(tendaysEns);
                        break;
                    case MONTH:
                        List<QmsPointMonthNumericData> monthEns = pointMonthNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (monthEns != null && monthEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.monthEntitiesToDtoList(monthEns);
                        break;
                    case QUARTER:
                        List<QmsPointQuarterNumericData> quarterEns = pointQuarterNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (quarterEns != null && quarterEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.quarterEntitiesToDtoList(quarterEns);
                        break;
                    case YEAR:
                        List<QmsPointYearNumericData> yearEns = pointYearNumericDataService.getManualSetQualityRightAffectDatas(pointid, beginPeriodTime, endPeriodTime);
                        if (yearEns != null && yearEns.size() > 0x0)
                            needSetQualityDatas = QmsPointNumericDataMapper.MAPPER.yearEntitiesToDtoList(yearEns);
                        break;
                    default:
                        break;
                }

                //如果设置数据质量需要修改的点号数据不为空时，逐条点号数据进行数据值有效性及防误处理判定
                if (needSetQualityDatas != null && needSetQualityDatas.size() > 0x0) {
                    for (QmsPointNumericDataDto pointData : needSetQualityDatas) {
                        LocalDateTime time = pointData.getTm();
                        QmsPointDataValueAlarmJudgeResultDto alarmErrorProofJudgeResult = pointDataValueAlarmJudgeService.judgeErrorProof(pointid, tmseg, setPointQualityOptype, pointData);
                        //在数据保存前判定的告警只需要处理触发防误处理的告警
                        if (alarmErrorProofJudgeResult != null && alarmErrorProofJudgeResult.isHasTriggerErrorProof() == true) {
                            //记录如果是人工录入/修改点号数据并且value数值触发告警防误处理，此时不对点号数据进行保存，返回前端提示信息
                            //每个每条告警规则，获取其中进行防误处理的需要修改数据值的字段信息，将错误提示信息添加到操作失败的集合中
                            for (QmsPointDataValueAlarmRuleJudgeResultDto ruleJudgeResult : alarmErrorProofJudgeResult.getRuleJudgeResults()) {
                                if (ruleJudgeResult != null && ruleJudgeResult.isHasTriggerErrorProof() == true) {
                                    //设置数据质量标记均为人工操作，如果触发防误处理不对点号数据质量进行修改，返回错误提示信息给前端
                                    if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.VALUE) {
                                        saveFails.put(time, "数据值错误，不能修改数据为正确数据。触发异常数据防误处理，防误处理信息[" + ruleJudgeResult.getErrorProofSetMsg() + "]");
                                        continue;
                                    } else if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.BEGIN_VALUE) {
                                        saveFails.put(time, "时段初数据值错误，不能修改数据为正确数据。触发异常数据防误处理，防误处理信息[" + ruleJudgeResult.getErrorProofSetMsg() + "]");
                                        continue;
                                    } else if (ruleJudgeResult.getTriggerErrorProofValueField() == QmsPointValueFieldEnum.END_VALUE) {
                                        saveFails.put(time, "时段末数据值错误，不能修改数据为正确数据。触发异常数据防误处理，防误处理信息[" + ruleJudgeResult.getErrorProofSetMsg() + "]");
                                        continue;
                                    }
                                }
                            }
                        } else {
                            //如果当前时间的点号数据不触发防误处理，继续进行设置点号数据质量操作
                            //根据不同的点号时段类型调用不同的service进行点号数据质量设置
                            switch (tmseg) {
                                case REAL:
                                case MINUTE:
                                    List<QmsPointRealNumericData> realEns = pointRealNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (realEns != null && realEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.realEntityToPointDto(realEns.get(0x0)));
                                    break;
                                case HOUR:
                                    List<QmsPointHourNumericData> hourEns = pointHourNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (hourEns != null && hourEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.hourEntityToDto(hourEns.get(0x0)));
                                    break;
                                case DAY:
                                    List<QmsPointDayNumericData> dayEns = pointDayNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (dayEns != null && dayEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.dayEntityToDto(dayEns.get(0x0)));
                                    break;
                                case WEEK:
                                    List<QmsPointWeekNumericData> weekEns = pointWeekNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (weekEns != null && weekEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.weekEntityToDto(weekEns.get(0x0)));
                                    break;
                                case TEN_DAYS:
                                    List<QmsPointTendaysNumericData> tendaysEns = pointTendaysNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (tendaysEns != null && tendaysEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.tendaysEntityToDto(tendaysEns.get(0x0)));
                                    break;
                                case MONTH:
                                    List<QmsPointMonthNumericData> monthEns = pointMonthNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (monthEns != null && monthEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.monthEntityToDto(monthEns.get(0x0)));
                                    break;
                                case QUARTER:
                                    List<QmsPointQuarterNumericData> quarterEns = pointQuarterNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (quarterEns != null && quarterEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.quarterEntityToDto(quarterEns.get(0x0)));
                                    break;
                                case YEAR:
                                    List<QmsPointYearNumericData> yearEns = pointYearNumericDataService.setQuality(pointid, time, time, quality, now);
                                    if (yearEns != null && yearEns.size() > 0x0)//设置数据质量的数据时标只是一个时间点，应该只有一条数据
                                        setQualityAffectDatas.add(QmsPointNumericDataMapper.MAPPER.yearEntityToDto(yearEns.get(0x0)));
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            } else {
                //如果本次操作是删除数据或设置为错误数据，不需要先查询受影响的点号数据，直接进行修改数据质量；通过点号数据消息传递到告警处理消费者中再判断是否要取消对应告警记录
                //根据不同的点号时段类型调用不同的service进行点号数据质量设置
                switch (tmseg) {
                    case REAL:
                    case MINUTE:
                        List<QmsPointRealNumericData> realEns = pointRealNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (realEns != null && realEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.realEntitiesToDtoPointList(realEns);
                        break;
                    case HOUR:
                        List<QmsPointHourNumericData> hourEns = pointHourNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (hourEns != null && hourEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.hourEntitiesToDtoList(hourEns);
                        break;
                    case DAY:
                        List<QmsPointDayNumericData> dayEns = pointDayNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (dayEns != null && dayEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.dayEntitiesToDtoList(dayEns);
                        break;
                    case WEEK:
                        List<QmsPointWeekNumericData> weekEns = pointWeekNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (weekEns != null && weekEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.weekEntitiesToDtoList(weekEns);
                        break;
                    case TEN_DAYS:
                        List<QmsPointTendaysNumericData> tendaysEns = pointTendaysNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (tendaysEns != null && tendaysEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.tendaysEntitiesToDtoList(tendaysEns);
                        break;
                    case MONTH:
                        List<QmsPointMonthNumericData> monthEns = pointMonthNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (monthEns != null && monthEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.monthEntitiesToDtoList(monthEns);
                        break;
                    case QUARTER:
                        List<QmsPointQuarterNumericData> quarterEns = pointQuarterNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (quarterEns != null && quarterEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.quarterEntitiesToDtoList(quarterEns);
                        break;
                    case YEAR:
                        List<QmsPointYearNumericData> yearEns = pointYearNumericDataService.setQuality(pointid, beginPeriodTime, endPeriodTime, quality, now);
                        if (yearEns != null && yearEns.size() > 0x0)
                            setQualityAffectDatas = QmsPointNumericDataMapper.MAPPER.yearEntitiesToDtoList(yearEns);
                        break;
                    default:
                        break;
                }
            }

            //数据流程日志：人工设置数据质量标记操作此方法直接供API接口调用，此处即为流程的开始
            //生成当前设置数据质量操作流程ID，同时作为全局流程和节点ID
            String flowid = IdentifierUtil.simpleUUID();
            //将被删除的点号数据集合发送至点号数据消息队列中
            QmsMqPointDataMessageDto pointDataMsg = new QmsMqPointDataMessageDto();
            pointDataMsg.setFlowid(flowid);
            pointDataMsg.setNodeid(flowid);
            pointDataMsg.setPointid(pointid);
            pointDataMsg.setTmseg(tmseg);
            //设置消息中的数据保存操作类型
            pointDataMsg.setOptype(setPointQualityOptype);
            pointDataMsg.setManual(true);//设置人工操作标记为true
            pointDataMsg.setUserName(userName);
            pointDataMsg.setQuality(quality);
            //设置删除数据或设置数据质量的起止时间
            pointDataMsg.setBeginTime(beginPeriodTime);
            pointDataMsg.setEndTime(endPeriodTime);

            //保存失败集合不为空时对结果进行赋值
            if (saveFails.size() > 0x0) {
                result.setFails(saveFails);
            }
            //判断设置数据质量受影响的数据是否有，如果没有修改任何点号数据保持返回结果中的是否成功为false
            if (setQualityAffectDatas != null && setQualityAffectDatas.size() > 0x0) {
                //修改数据质量标记成功，设置返回标记为成功
                result.setSuccess(true);
                result.setAffectCount(setQualityAffectDatas.size());

                //获取实际受影响的点号数据时标时间段
                LocalDateTime affectBeginDataTime = null;
                LocalDateTime affectEndDataTime = null;
                //数据时标最大的有效数据，用于后面更新点号数据最新状态
                QmsPointNumericDataDto maxTimeValidPointData = null;

                //设置消息中的实际受影响点号数据集合，点号数据消息中直接根据受影响的数据进行处理
                //设置数据质量时，只有数据质量和uptm变化，受影响数据记录中已有数据保持为null
                List<QmsPointDataSaveAffectDto> affectDatas = new LinkedList<>();
                for (QmsPointNumericDataDto data : setQualityAffectDatas) {
                    LocalDateTime time = data.getTm();
                    //设置实际受影响的最小和最大数据时标
                    if (affectBeginDataTime == null || affectBeginDataTime.isAfter(time))
                        affectEndDataTime = time;
                    if (affectEndDataTime == null || affectEndDataTime.isBefore(time))
                        affectEndDataTime = time;

                    //仅在操作为人工设置为有效数据时需要获取时标最大的点号数据
                    if (setPointQualityOptype == QmsDataFlowOperateTypeEnum.SET_POINT_DATA_IS_RIGHT) {
                        if (maxTimeValidPointData == null || maxTimeValidPointData.getTm().isAfter(time) == false)
                            maxTimeValidPointData = data;
                    }

                    QmsPointDataSaveAffectDto affectData = new QmsPointDataSaveAffectDto();
                    affectData.setData(data);
                    //设置ops为设置数据质量
                    affectData.setOps(QmsPointDataSaveAffectEnum.QUALITY.getIndex());
                    //已有数据对象保持为null
                    affectData.setExistData(null);

                    affectDatas.add(affectData);
                }
                pointDataMsg.setAffectDatas(affectDatas);

                //发送数据流程日志消息
                StringBuilder sbSaveDataFlowResult = new StringBuilder();
                sbSaveDataFlowResult.append("本次设置点号数据质量共修改数据[" + setQualityAffectDatas.size() + "]条");
                if (saveFails.size() > 0x0) {
                    for (Map.Entry<LocalDateTime, String> failEntry : saveFails.entrySet()) {
                        sbSaveDataFlowResult.append("\n数据时标[" + QmsTimeUtil.format(failEntry.getKey(), tmseg) + "] 失败信息：" + failEntry.getValue());
                    }
                }
                dataFlowLogProducer.send(flowid, flowid, null, setPointQualityOptype, true, now, null,
                        true, sbSaveDataFlowResult.toString(), pointid, tmseg, affectBeginDataTime, affectEndDataTime);

                //修改数据质量标记后需要更新最新点号状态中的信息
                updatePointDataLatest(pointid, tmseg, affectEndDataTime, maxTimeValidPointData);

                //发送点号数据消息
                pointDataProducer.send(pointDataMsg);
            } else {
                //未修改任何数据时保持操作是否成功为false，表示操作失败
                result.setMsg("本次设置点号数据质量没有进行任何数据的修改");
                //没有受影响的数据，发送数据流程日志消息
                dataFlowLogProducer.send(flowid, flowid, null, setPointQualityOptype, true, now, null,
                        false, "本次设置点号数据质量没有进行任何数据的修改", pointid, tmseg, beginPeriodTime, endPeriodTime);
            }

            return result;
        } catch (QCPromptException ex) {
            //如果修改数据质量标记时异常返回异常信息作为错误提示信息
            result.setMsg("设置点号数据质量错误，" + ex.getMessage());

            return result;
        } catch (Exception ex) {
            //如果修改数据质量标记时异常返回异常信息作为错误提示信息
            result.setMsg("设置点号数据质量异常，" + ex.getMessage());

            return result;
        }
    }

    /**
     * 新增insert点号数据，只进行insert操作，仅供此service内部调用
     *
     * @param pointid 点号编码
     * @param tmseg   点号时段类型
     * @param data    点号数据对象
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/10/1
     */
    String add(String pointid, QmsPointTimeSegmentEnum tmseg, QmsPointNumericDataDto data) {
        //验证输入：点号不能为空、时段类型必须有效、数据对象不能为空（时间必须有、值不可以为空、特征值不判断是否为空）
        if (StringUtils.isBlank(pointid))
            return "新增点号数据时点号不能为空";
        if (tmseg == null)
            return "新增点号数据时点号时段类型不能为空";
        if (tmseg == QmsPointTimeSegmentEnum.UN_DEFINE)
            return "新增点号数据时点号时段类型错误";
        if (data == null)
            return "新增点号数据时点号数据对象不能为空";
        if (data.getTm() == null)
            return "新增点号数据时点号数据时标不能为空";
        if (data.getV() == null)
            return "新增点号数据时点号数据值不能为空";
        if (data.getV().isNaN() == true)
            return "新增点号数据时点号数据值不能为非数字";

        //根据是实时数据还是小时及日以上数据分别转换为对应的Entity
        if (tmseg == QmsPointTimeSegmentEnum.REAL || tmseg == QmsPointTimeSegmentEnum.MINUTE) {
            //实时和分钟均当做实时点号
            QmsPointRealNumericData dataEntity = new QmsPointRealNumericData();
            dataEntity.setPointid(pointid);
            dataEntity.setTm(data.getTm());
            dataEntity.setV(data.getV());
            dataEntity.setQuality(data.getQuality());
            //更新时间如果为空，默认为当前时间
            if (data.getUptm() == null)
                dataEntity.setUptm(LocalDateTimeUtil.getNow());
            else
                dataEntity.setUptm(data.getUptm());

            return pointRealNumericDataService.add(dataEntity);
        } else {
            //时段类型为小时及以上
            QmsPointNumericData dataEntity = new QmsPointDayNumericData();
            dataEntity.setPointid(pointid);
            dataEntity.setTm(data.getTm());
            dataEntity.setV(data.getV());
            dataEntity.setQuality(data.getQuality());
            //更新时间如果为空，默认为当前时间
            if (data.getUptm() == null)
                dataEntity.setUptm(LocalDateTimeUtil.getNow());
            else
                dataEntity.setUptm(data.getUptm());
            //其他特征值字段如果为空时保持为null
            if (data.getBeginv() == null)
                dataEntity.setBeginv(null);
            else
                dataEntity.setBeginv(data.getBeginv());
            if (data.getEndv() == null)
                dataEntity.setEndv(null);
            else
                dataEntity.setEndv(data.getEndv());
            if (data.getMaxv() == null)
                dataEntity.setMaxv(null);
            else
                dataEntity.setMaxv(data.getMaxv());
            if (data.getMaxtm() == null)
                dataEntity.setMaxtm(null);
            else
                dataEntity.setMaxtm(data.getMaxtm());
            if (data.getMinv() == null)
                dataEntity.setMinv(null);
            else
                dataEntity.setMinv(data.getMinv());
            if (data.getMintm() == null)
                dataEntity.setMintm(null);
            else
                dataEntity.setMintm(data.getMintm());

            //根据不同的点号时段类型调用不同的service进行数据查询
            switch (tmseg) {
                case HOUR:
                    QmsPointHourNumericData hourEntity = new QmsPointHourNumericData();
                    BeanUtils.copyProperties(dataEntity, hourEntity);

                    return pointHourNumericDataService.add(hourEntity);
                case DAY:
                    QmsPointDayNumericData dayEntity = new QmsPointDayNumericData();
                    BeanUtils.copyProperties(dataEntity, dayEntity);

                    return pointDayNumericDataService.add(dayEntity);
                case WEEK:
                    QmsPointWeekNumericData weekEntity = new QmsPointWeekNumericData();
                    BeanUtils.copyProperties(dataEntity, weekEntity);

                    return pointWeekNumericDataService.add(weekEntity);
                case TEN_DAYS:
                    QmsPointTendaysNumericData tendaysEntity = new QmsPointTendaysNumericData();
                    BeanUtils.copyProperties(dataEntity, tendaysEntity);

                    return pointTendaysNumericDataService.add(tendaysEntity);
                case MONTH:
                    QmsPointMonthNumericData monthEntity = new QmsPointMonthNumericData();
                    BeanUtils.copyProperties(dataEntity, monthEntity);

                    return pointMonthNumericDataService.add(monthEntity);
                case QUARTER:
                    QmsPointQuarterNumericData quarterEntity = new QmsPointQuarterNumericData();
                    BeanUtils.copyProperties(dataEntity, quarterEntity);

                    return pointQuarterNumericDataService.add(quarterEntity);
                case YEAR:
                    QmsPointYearNumericData yearEntity = new QmsPointYearNumericData();
                    BeanUtils.copyProperties(dataEntity, yearEntity);

                    return pointYearNumericDataService.add(yearEntity);
                default:
                    break;
            }
        }

        return QCUnifyReturnValue.Success();
    }

    /**
     * 修改update点号数据，只进行update操作，仅供此service内部调用
     *
     * @param pointid   点号编码
     * @param tmseg     点号时段类型
     * @param data      点号数据对象
     * @param ops       操作值，为枚举类QmsPointDataSaveAffectEnum的值之和
     * @param existData 已有的点号数据对象
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2025/10/1
     */
    String update(String pointid, QmsPointTimeSegmentEnum tmseg, QmsPointNumericDataDto data, int ops, QmsPointNumericDataDto existData) {
        //验证输入：点号不能为空、时段类型必须有效、数据对象不能为空（时间必须有、值和特征值不判断是否为空）
        //操作值必须＞0，原数据必须有（先初始化Entity为原数据，再根据操作值进行更新对应的字段值）
        if (StringUtils.isBlank(pointid))
            return "修改点号数据时点号不能为空";
        if (tmseg == null)
            return "修改点号数据时点号时段类型不能为空";
        if (tmseg == QmsPointTimeSegmentEnum.UN_DEFINE)
            return "修改点号数据时点号时段类型错误";
        if (data == null)
            return "修改点号数据时点号数据对象不能为空";
        if (data.getTm() == null)
            return "修改点号数据时点号数据时标不能为空";
        if (ops <= 0x0)
            return "修改点号数据时修改属性值错误，没有需要修改的属性值";
        if (existData == null)
            return "修改点号数据时已有点号数据对象不能为空";
        if (existData.getTm() == null)
            return "修改点号数据时已有点号数据时标不能为空";
        //判断修改的点号数据和已有数据的时间是否一致
        if (data.getTm().isEqual(existData.getTm()) == false)
            return "修改点号数据时点号数据时标与已有点号数据时标不一致";

        //根据是实时数据还是小时及日以上数据分别转换为对应的Entity
        if (tmseg == QmsPointTimeSegmentEnum.REAL || tmseg == QmsPointTimeSegmentEnum.MINUTE) {
            //实时和分钟均当做实时点号
            QmsPointRealNumericData dataEntity = new QmsPointRealNumericData();
            dataEntity.setPointid(pointid);
            dataEntity.setTm(data.getTm());
            dataEntity.setV(data.getV());
            dataEntity.setQuality(data.getQuality());
            //更新时间如果为空，默认为当前时间
            if (data.getUptm() == null)
                dataEntity.setUptm(LocalDateTimeUtil.getNow());
            else
                dataEntity.setUptm(data.getUptm());

            return pointRealNumericDataService.update(dataEntity);
        } else {
            //时段类型为小时及以上
            QmsPointNumericData dataEntity = new QmsPointDayNumericData();
            dataEntity.setPointid(pointid);
            dataEntity.setTm(data.getTm());
            dataEntity.setQuality(data.getQuality());
            //更新时间如果为空，默认为当前时间
            if (data.getUptm() == null)
                dataEntity.setUptm(LocalDateTimeUtil.getNow());
            else
                dataEntity.setUptm(data.getUptm());
            //先将entity的属性值初始化为已有数据的值
            dataEntity.setV(existData.getV());
            dataEntity.setBeginv(existData.getBeginv());
            dataEntity.setEndv(existData.getEndv());
            dataEntity.setMaxtm(existData.getMaxtm());
            dataEntity.setMaxv(existData.getMaxv());
            dataEntity.setMintm(existData.getMintm());
            dataEntity.setMinv(existData.getMinv());
            //根据传入的操作值ops进行更新属性的赋值
            QmsPointDataSaveAffectEnum affectEnum = QmsPointDataSaveAffectEnum.VALUE;
            if ((ops & affectEnum.getIndex()) == affectEnum.getIndex()) {
                if (data.getV() == null)
                    dataEntity.setV(null);
                else
                    dataEntity.setV(data.getV());
            }
            affectEnum = QmsPointDataSaveAffectEnum.BEGINV;
            if ((ops & affectEnum.getIndex()) == affectEnum.getIndex()) {
                if (data.getBeginv() == null)
                    dataEntity.setBeginv(null);
                else
                    dataEntity.setBeginv(data.getBeginv());
            }
            affectEnum = QmsPointDataSaveAffectEnum.ENDV;
            if ((ops & affectEnum.getIndex()) == affectEnum.getIndex()) {
                if (data.getEndv() == null)
                    dataEntity.setEndv(null);
                else
                    dataEntity.setEndv(data.getEndv());
            }
            affectEnum = QmsPointDataSaveAffectEnum.MAX;
            if ((ops & affectEnum.getIndex()) == affectEnum.getIndex()) {
                if (data.getMaxv() == null)
                    dataEntity.setMaxv(null);
                else
                    dataEntity.setMaxv(data.getMaxv());
                if (data.getMaxtm() == null)
                    dataEntity.setMaxtm(null);
                else
                    dataEntity.setMaxtm(data.getMaxtm());
            }
            affectEnum = QmsPointDataSaveAffectEnum.MIN;
            if ((ops & affectEnum.getIndex()) == affectEnum.getIndex()) {
                if (data.getMinv() == null)
                    dataEntity.setMinv(null);
                else
                    dataEntity.setMinv(data.getMinv());
                if (data.getMintm() == null)
                    dataEntity.setMintm(null);
                else
                    dataEntity.setMintm(data.getMintm());
            }

            //根据不同的点号时段类型调用不同的service进行数据查询
            switch (tmseg) {
                case HOUR:
                    QmsPointHourNumericData hourEntity = new QmsPointHourNumericData();
                    BeanUtils.copyProperties(dataEntity, hourEntity);

                    return pointHourNumericDataService.update(hourEntity);
                case DAY:
                    QmsPointDayNumericData dayEntity = new QmsPointDayNumericData();
                    BeanUtils.copyProperties(dataEntity, dayEntity);

                    return pointDayNumericDataService.update(dayEntity);
                case WEEK:
                    QmsPointWeekNumericData weekEntity = new QmsPointWeekNumericData();
                    BeanUtils.copyProperties(dataEntity, weekEntity);

                    return pointWeekNumericDataService.update(weekEntity);
                case TEN_DAYS:
                    QmsPointTendaysNumericData tendaysEntity = new QmsPointTendaysNumericData();
                    BeanUtils.copyProperties(dataEntity, tendaysEntity);

                    return pointTendaysNumericDataService.update(tendaysEntity);
                case MONTH:
                    QmsPointMonthNumericData monthEntity = new QmsPointMonthNumericData();
                    BeanUtils.copyProperties(dataEntity, monthEntity);

                    return pointMonthNumericDataService.update(monthEntity);
                case QUARTER:
                    QmsPointQuarterNumericData quarterEntity = new QmsPointQuarterNumericData();
                    BeanUtils.copyProperties(dataEntity, quarterEntity);

                    return pointQuarterNumericDataService.update(quarterEntity);
                case YEAR:
                    QmsPointYearNumericData yearEntity = new QmsPointYearNumericData();
                    BeanUtils.copyProperties(dataEntity, yearEntity);

                    return pointYearNumericDataService.update(yearEntity);
                default:
                    break;
            }
        }

        return QCUnifyReturnValue.Success();
    }

    public QmsPointDataManageResultDto batchSave(String pointid, QmsPointTimeSegmentEnum tmseg, List<QmsPointNumericDataDto> datas) {
        //提供一个批量保存点号数据方法，用于导入点号数据或同步点号数据批量写入；
        //批量保存方法自动根据本次数据时标的起止时间段，先删除数据，再批量写入
        //批量保存方法不记录到流程日志，也不触发点号数据消息

        return null;
    }

    /**
     * 更新点号数据最新状态
     *
     * @param pointid 点号
     * @param tmseg   时段类型
     * @param time    操作的最大数据时标，可以为空，在设置数据质量时传入设置的最大时间
     * @param data    数据时标最大的有效数据，可以为空
     * @return void
     * @author QuCheng Tech
     * @since 2025/10/10
     */
    void updatePointDataLatest(String pointid, QmsPointTimeSegmentEnum tmseg, LocalDateTime time, QmsPointNumericDataDto data) {
        try {
            //验证基本输入错误不进行处理
            if (StringUtils.isNotBlank(pointid) && tmseg != null && tmseg != QmsPointTimeSegmentEnum.UN_DEFINE) {
                //如果time和data同时为空不进行处理，time表示设置数据质量标记的数据时标、data表示新增或修改的最新数据
                if (time != null || data != null) {
                    if (data != null) {
                        //有数据新增或修改，调用更新方法，由更新方法自行判断是否要更新
                        pointDataLatestService.updatePointData(pointid, tmseg, false, data);
                    } else {
                        //如果没有传入data，表示本次不是新增或修改数据；如果有传入data则为本次新增或修改的时标最大的有效数据，使用该数据更新点号最新状态
                        //没有数据新增或修改，判定为时设置数据质量标记
                        //先获取点号最新状态，如果点号中的最新状态数据时标＞本次操作的时间判定为不需要更新，否则获取最新数据进行更新
                        //最新点号状态记录中数据时标＞本次操作的最大数据时标，本次操作的数据并不需要更新点号最新状态
                        QmsPointDataLatest pointDataLatest = pointDataLatestService.get(pointid, tmseg);
                        if (pointDataLatest == null || (pointDataLatest.getTm() != null && pointDataLatest.getTm().isAfter(time) == false)) {
                            //获取最新一条有效的点号数据
                            QmsPointNumericDataDto noMoreThanData = pointDataQueryService.getNoMoreThan(pointid, tmseg, time, QmsConstant.POINT_DATA_VALID_QUALITY_FROM);
                            if (noMoreThanData != null && noMoreThanData.getTm() != null) {
                                pointDataLatestService.updatePointData(pointid, tmseg, false, noMoreThanData);
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
            logger.error("updatePointDataLatest 更新点号数据最新状态时异常，" + ex.getMessage());
        }
    }
}
